iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0
Mobile Development

花30天做個Android小專案系列 第 15

Day15 - Ptt換頁及新增文章列表項目

  • 分享至 

  • xImage
  •  

今天的內容算是當初一時沒考慮到的東西。

主要是Ptt一頁的文章最多列出20篇,若要搜尋到20篇以前的文章就需要換頁,幸運的是文章列表這邊換頁的指令蠻簡單的。

Ptt文章列表的換頁指令

https://ithelp.ithome.com.tw/upload/images/20210929/20124602b1MadN95Nr.png
可以看到在這邊可以簡單的用"P"和"N"換頁。

Adapter

實作上首先在SearchArticleResultAdapter新增callback來通知更新

//...
var needMoreArticleCallback: (() -> Unit)? = null

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.bindView(articleList[position])
    if (position == articleList.size - 1 && articleList[position].number != "1") {
        needMoreArticleCallback?.invoke()
    }
}
//...

如果目前列表的最後一項編號不是1的話代表還有舊的內容,此時呼叫通知更新的方法。

needMoreArticleCallback

//...
articleAdapter.needMoreArticleCallback = {
    if (PttClient.getInstance().expect(inBoardPattern) == 2) {
        (requireActivity() as MainActivity).showLoading("")
        viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
            val ret = withContext(Dispatchers.IO) {
                PttClient.getInstance().send("P")
                delay(200L)
                PttClient.getInstance().expect(inBoardPattern)
            }
            if (ret == 2) {
                parseBoardArticle(PttClient.getInstance().getScreen())
            }
            (requireActivity() as MainActivity).dismissLoading()
        }
    }
}
//...

首先先用inBoardPattern確認目前畫面在看板內再送後續指令,如前面所述送出P後確認畫面依舊在看板內就送入parseBoardArticle方法內了。

改寫parseBoardArticle

為了保留現有內容並在後面新增文章,對此方法作了一點修改。

private val articleList = mutableListOf<Article>()

private fun parseBoardArticle(result: String) {
    val articlePattern =
        Pattern.compile("([●]|[ ])*(?<no>[0-9]+)[ ].([0-9 X]+|爆)(?<date>../..)[ ](?<author>.*?)([\\s□轉]|R:)+(?<title>.*)")
    val rows = result.split("\n")
    val currentArticleList = mutableListOf<Article>()
    for (row in rows) {
        val matcher = articlePattern.matcher(row)
        if (matcher.find()) {
            val article = Article(
                matcher.group("no")?.trim(),
                matcher.group("date")?.trim(),
                matcher.group("author")?.trim(),
                matcher.group("title")?.trim()
            )
            if (!articleList.any { it.number == article.number }) {
                currentArticleList.add(0, article)
            }
            Log.d(mTag, "article=$article")
        }
    }

    articleList.addAll(currentArticleList)
    articleAdapter.setData(articleList)
}

首先將articleList提出成全域變數,在解析目前頁面時另外宣告currentArticleList來儲存,最後再加入到articleList中。過程中則是用any來找出目前解析出的文章編號是否已經被加入過了,這是因為若剩餘未顯示的文章篇數不到20篇的話,在切換頁面後會用已顯示過的文章來補足20篇的篇數,因此有重複的可能。

articleList除了這邊使用到以外,再先前的搜尋操作前要注意先將內容清除掉,以避免重複。

private suspend fun searchDetailProcess(command: String): Boolean {
    articleList.clear()
    //...
}

private fun refreshSearch() {
    articleList.clear()
    //...
}

articleAdapter.setData

最後稍微看一下設置及更新資料的部分。

fun setData(newList: List<Article>) {
    val result = DiffUtil.calculateDiff(ArticleDiffUtilCallbackImpl(articleList, newList))
    articleList.clear()
    articleList.addAll(newList)
    result.dispatchUpdatesTo(this)
}

主要就是使用Android提供的DiffUtil來做更新,並實作DiffUtil.Callback

ArticleDiffUtilCallbackImpl

class ArticleDiffUtilCallbackImpl(
    private val oldList: List<Article>,
    private val newList: List<Article>
) : DiffUtil.Callback() {
    override fun getOldListSize(): Int = oldList.size

    override fun getNewListSize(): Int = newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] == newList[newItemPosition]
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition].number == newList[newItemPosition].number
    }
}

目前畫面

https://i.imgur.com/6fLHCim.gif


上一篇
Day14 - 解析看板文章及顯示
下一篇
Day16 - 進入和退出文章
系列文
花30天做個Android小專案30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言